/*******************************************************************************
 * Copyright (c) 2007 University of Oslo.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     University of Oslo - initial API and implementation
 *******************************************************************************/

package com.cea.papyrus.diagram.sequence.sedi.edit.handlers;

import static com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil.getCoveredBys;
import static com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil.getLifelines;
import static java.lang.Math.max;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.uml2.uml.CombinedFragment;
import org.eclipse.uml2.uml.Continuation;
import org.eclipse.uml2.uml.ExecutionOccurrenceSpecification;
import org.eclipse.uml2.uml.Interaction;
import org.eclipse.uml2.uml.InteractionFragment;
import org.eclipse.uml2.uml.InteractionUse;
import org.eclipse.uml2.uml.Lifeline;
import org.eclipse.uml2.uml.OccurrenceSpecification;
import org.eclipse.uml2.uml.StateInvariant;

import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagram.sequence.sedi.edit.EOEditPart;
import com.cea.papyrus.diagram.sequence.sedi.edit.StateInvariantEditPart;
import com.cea.papyrus.diagram.sequence.sedi.edit.TerminateEditPart;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.AnalyseCoveredByCommand;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.AnalyseFragmentMarginsCommand;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.RequestUtils;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.SetFragmentMarginsCommand;
import com.cea.papyrus.diagram.sequence.sedi.figures.SeDiSubpartFigure;
import com.cea.papyrus.diagram.sequence.sedi.model.command.CoveredByCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.command.DelegatingCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.command.SetConstraintCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;

/**
 * Handles automatic layout of Lifelines.
 * 
 * @author frankd@ifi.uio.no
 */
public class LifelineLayoutHandler implements LayoutHandler<Lifeline> {

    // public final EditPolicy hostPolicy;

    // TODO Remove one of the constructors
    public LifelineLayoutHandler() {
        // hostPolicy = null;
    }

    // public LifelineLayoutHandler(final EditPolicy hostPolicy) {
    // this.hostPolicy = hostPolicy;
    // }

    @SuppressWarnings("unchecked")
    public Rectangle layout(Diagram diagram, Lifeline lifeline) {
        // TODO Calculate horizontal position in a way that respects lifeline order
        // The current algorithm just positions new lifelines to the right of
        // all existing ones
        final Interaction owningInteraction = lifeline.getInteraction();
        final GraphNode interactionView = ModelUtil.getGraphNode(owningInteraction, diagram);

        int rightmost = 0;
        for (Lifeline lf : getLifelines(owningInteraction)) {
            final GraphNode lifelineView = ModelUtil.getGraphNode(lf, diagram);
            if (lifelineView != null && lifelineView.getPosition() != null
                    && lifelineView.getSize() != null) {
                // will be null for any new lifelines
                Rectangle bounds =
                        new Rectangle(lifelineView.getPosition(), lifelineView.getSize());
                rightmost = max(rightmost, bounds.right());
            }
        }

        return new Rectangle(rightmost + 40, 40, 100, interactionView.getSize().height - 40 - 5);
    }

    @SuppressWarnings("unchecked")
    public Command[] getMoveCommands(final EditPart lifelinePart, final Rectangle suggestedBounds) {

        final List<Command> cmds = new LinkedList<Command>();

        // the lifeline
        final GraphNode lifelineView = (GraphNode) lifelinePart.getModel();
        final SeDiSubpartFigure lifelineFigure =
                (SeDiSubpartFigure) ((GraphicalEditPart) lifelinePart).getFigure();
        final Lifeline lifeline = ModelUtil.<Lifeline> getSemantic(lifelineView);

        // some useful references
        final Diagram diagram = LookForElement.getDiagram(lifelineView);
        final Map<Object, EditPart> editPartRegistry =
                (Map<Object, EditPart>) lifelinePart.getViewer().getEditPartRegistry();

        // AnalyseFragmentMarginsCommand
        // All covered fragments need to record their margins
        // SetConstraintCommand
        // Move the lifeline
        // SetFragmentMarginsCommand
        // Find new bounds for fragment so that margins are maintained
        // [MoveFragmentCommand]
        // Ask each fragment to move to the new bounds
        // AnalyseCoveredByCommand
        // CoveredByCommand
        // Moving the lifeline may have brought additional fragments
        // over the lifeline.

        // Step 1
        final List<InteractionFragment> coveredList = getCoveredBys(lifeline);
        final List<AnalyseFragmentMarginsCommand> analyseCmds =
                new ArrayList<AnalyseFragmentMarginsCommand>(coveredList.size());
        for (InteractionFragment fragment : coveredList) {
            InteractionFragment analyseFragment = getAnalyseFragment(fragment);
            if (analyseFragment != null) {
                AnalyseFragmentMarginsCommand cmd = new AnalyseFragmentMarginsCommand();
                GraphNode fragmentView = ModelUtil.getGraphNode(analyseFragment, diagram);
                if (fragmentView != null) {
                    cmd.setFragment(fragmentView);
                    analyseCmds.add(cmd);
                    cmds.add(cmd);
                }
            }
        }

        // Step 2
        final Rectangle preferredBounds = lifelineFigure.getPreferredBounds(suggestedBounds);
        cmds.add(new SetConstraintCommand(lifelineView, preferredBounds));

        // Step 3
        List<Command> fragmentMoveCommands = new ArrayList<Command>(analyseCmds.size());
        for (final AnalyseFragmentMarginsCommand analyseCmd : analyseCmds) {
            final DelegatingCommand moveCommand = new DelegatingCommand();
            Command glue = new Command() {
                public void execute() {
                    final SetFragmentMarginsCommand decideCmd = new SetFragmentMarginsCommand();
                    decideCmd.setFragment(analyseCmd.getFragment());
                    decideCmd.setLeftMargin(analyseCmd.getLeftMargin());
                    decideCmd.setRightMargin(analyseCmd.getRightMargin());
                    decideCmd.execute();

                    GraphNode fragmentView = analyseCmd.getFragment();
                    EditPart fragmentEP = editPartRegistry.get(fragmentView);

                    CompoundCommand actualMoveCmd = new CompoundCommand();
                    if (fragmentEP instanceof TerminateEditPart) {
                    	Command[] cmds = 
                    		new StopLayoutHandler().getMoveCommands(fragmentEP, decideCmd.getNewBounds());
                        if (cmds != null) {
                            for (Command c : cmds)
                            	actualMoveCmd.add(c);
                        }
                        
                    } else if (fragmentEP instanceof StateInvariantEditPart) {
                    	Command[] cmds = 
                    		new StateInvariantLayoutHandler().getMoveCommands(fragmentEP, decideCmd.getNewBounds());
                        if (cmds != null) {
                            for (Command c : cmds)
                            	actualMoveCmd.add(c);
                        }
                    
                    } else if (fragmentEP instanceof EOEditPart) {
                    	Command[] cmds = 
                    		new ExecutionSpecificationLayoutHandler().getMoveCommands(fragmentEP, decideCmd.getNewBounds());
                        if (cmds != null) {
                            for (Command c : cmds)
                            	actualMoveCmd.add(c);
                        }
                        
                    } else {
                        Request moveRequest =
                            RequestUtils.createMoveRequest(fragmentEP, decideCmd.getNewBounds());
                    	Command cmd = fragmentEP.getCommand(moveRequest);
                    	if (cmd != null) {
                    		actualMoveCmd.add(cmd);	
                    	}                    	
                    }

                    moveCommand.setCommand(actualMoveCmd);
                }
            };

            cmds.add(glue);
            fragmentMoveCommands.add(moveCommand);
        }

        // Step 4
        final AnalyseCoveredByCommand analyseCoveredCmd = new AnalyseCoveredByCommand();
        analyseCoveredCmd.setEditPart(lifelinePart);
        analyseCoveredCmd.setLifelineView(lifelineView);

        final CoveredByCommand coverCmd = new CoveredByCommand();
        coverCmd.setLifeline(lifeline);

        Command glue = new Command() {
            public void execute() {
                coverCmd.setCoveredBy(analyseCoveredCmd.getCoveredBy());
            }
        };

        cmds.add(analyseCoveredCmd);
        cmds.add(glue);
        cmds.add(coverCmd);

        // It is important to complete the movement of the lifeline first
        // before the covered fragments are moved too. Otherwise, the
        // fragments may - from their own cover analysis - come to expect
        // additional fragments to cover the lifeline (but that do not yet
        // in fact cover the lifeline because it has not technically been
        // moved). As a consequence they get their own position on the
        // lifeline wrong.
        cmds.addAll(fragmentMoveCommands);

        return cmds.toArray(new Command[cmds.size()]);
    }

    /**
     * Avgj�r om det gitte fragmentet er et fragment som kan bli p�virket av flyttingen av
     * Lifelinen.
     */
    private InteractionFragment getAnalyseFragment(final InteractionFragment fragment) {
        
        if (fragment instanceof ExecutionOccurrenceSpecification) {
            ExecutionOccurrenceSpecification event = (ExecutionOccurrenceSpecification) fragment;
            if (event.getExecution() != null)
                return event.getExecution();
        }
        
    	if (fragment instanceof CombinedFragment || fragment instanceof Continuation
                || fragment instanceof InteractionUse || fragment instanceof StateInvariant
                || fragment instanceof OccurrenceSpecification) {
            return fragment;
    	}

        return null;
    }
}
